package sk.tuke.serviceImpl;

import java.util.ArrayList;
import java.util.List;

import javax.jws.WebMethod;
import javax.ws.rs.core.Response;

import sk.tuke.domain.Address;
import sk.tuke.domain.Doctor;
import sk.tuke.domain.Patient;
import sk.tuke.domain.Phone;
import sk.tuke.domain.User;
import sk.tuke.domain.IO.userManagemet.CreateDoctorInput;
import sk.tuke.domain.IO.userManagemet.CreateDoctorOutput;
import sk.tuke.domain.IO.userManagemet.CreatePatientInput;
import sk.tuke.domain.IO.userManagemet.CreatePatientOutput;
import sk.tuke.domain.IO.userManagemet.DeleteDoctorInput;
import sk.tuke.domain.IO.userManagemet.DeleteDoctorOutput;
import sk.tuke.domain.IO.userManagemet.DeletePatientInput;
import sk.tuke.domain.IO.userManagemet.DeletePatientOutput;
import sk.tuke.domain.IO.userManagemet.GetDoctorsInput;
import sk.tuke.domain.IO.userManagemet.GetDoctorsOutput;
import sk.tuke.domain.IO.userManagemet.GetPatientsInput;
import sk.tuke.domain.IO.userManagemet.GetPatientsOutput;
import sk.tuke.domain.IO.userManagemet.LoginInput;
import sk.tuke.domain.IO.userManagemet.LoginOutput;
import sk.tuke.domain.IO.userManagemet.UpdateDoctorInput;
import sk.tuke.domain.IO.userManagemet.UpdateDoctorOutput;
import sk.tuke.domain.IO.userManagemet.UpdatePatientInput;
import sk.tuke.domain.IO.userManagemet.UpdatePatientOutput;
import sk.tuke.persistance.AddressPo;
import sk.tuke.persistance.DepartmentPo;
import sk.tuke.persistance.DoctorPo;
import sk.tuke.persistance.InsurancePo;
import sk.tuke.persistance.PatientPo;
import sk.tuke.persistance.PhonePo;
import sk.tuke.persistance.UserPo;
import sk.tuke.serviceDao.AbstractDao;
import sk.tuke.serviceDao.UserManagementDao;
import sk.tuke.serviceInterface.UserManagementService;
import sk.tuke.utils.MapperDomainToPo;
import sk.tuke.utils.MapperPOtoDomain;
import sk.tuke.utils.Utils;

public class UserManagementServiceImpl extends CommonService implements UserManagementService {

	private MapperDomainToPo mapperDomainToPo;

	private MapperPOtoDomain mapperPoToDomain;

	private UserManagementDao userManagementDao;

	private AbstractDao abstractDao;

	@WebMethod
	public Response createDoctor(CreateDoctorInput createUserInput) throws Throwable {
		try {
			Utils.checkObjectToNull(new String[] { "createUserInput" }, createUserInput);
			Doctor doctor = createUserInput.getDoctor();
			Utils.checkObjectToNull(new String[] { "doctor" }, doctor);
			Utils.checkObjectToNull(new String[] { "insuranceNumber", "departmentNumber" }, doctor.getInsuranceNumber(), doctor.getDepartmentNumber());
			List<AddressPo> addressesPo = new ArrayList<AddressPo>();
			List<PhonePo> phonesPo = new ArrayList<PhonePo>();

			InsurancePo insurancePo = abstractDao.findEntityByNumber(InsurancePo.class, doctor.getInsuranceNumber());
			if (insurancePo == null) {
				throw new IllegalArgumentException("Can not find insurance by number [" + doctor.getInsuranceNumber() + "] in database.");
			}

			DepartmentPo departmentPo = abstractDao.findEntityByNumber(DepartmentPo.class, doctor.getDepartmentNumber());
			if (departmentPo == null) {
				throw new IllegalArgumentException("Can not find department by number [" + doctor.getDepartmentNumber() + "] in database.");
			}
			if (doctor.getAddress() != null) {
				for (Address address : doctor.getAddress()) {
					Utils.checkObjectToNull(new String[] { "address.name", "address.street", "address.streetNum", "address.country",
							"address.zip", "address.city" }, address.getName(), address.getStreet(), address.getStreetNum(), address.getCountry(),
							address.getZip(), address.getCity());
					addressesPo.add(mapperDomainToPo.mapAddressToAddressPo(address));
				}
			}

			if (doctor.getPhone() != null) {
				for (Phone phone : doctor.getPhone()) {
					Utils.checkObjectToNull(new String[] { "phone.phoneNumber" }, phone.getPhoneNumber());
					phonesPo.add(mapperDomainToPo.mapPhoneToPhonePo(phone));
				}
			}

			DoctorPo doctorPo = userManagementDao.createDoctor(doctor.getFirstname(), doctor.getMiddlename(), doctor.getLastname(), addressesPo,
					doctor.getNumber(), doctor.getPassword(), doctor.getEmail(), phonesPo, doctor.getSalutation(), insurancePo, departmentPo);

			CreateDoctorOutput result = new CreateDoctorOutput();
			doctor = (Doctor) mapperPoToDomain.mapUserPoToUser(doctorPo);
			removeSensitiveInfo(doctor);
			result.setDoctor(doctor);
			return Response.status(Response.Status.CREATED).entity(result).build();
		} catch (Exception e) {
			return handleException(CreateDoctorOutput.class, e, Response.Status.BAD_REQUEST);
		}

	}

	@WebMethod
	public Response getDoctors(GetDoctorsInput getUserInput) throws Throwable {
		try {
			Utils.checkObjectToNull(new String[] { "getUserInput" }, getUserInput);
			List<Doctor> doctors = new ArrayList<Doctor>();
			List<DoctorPo> doctorPos = userManagementDao.getDoctors(getUserInput.getNumber());
			if (doctorPos.size() == 0) {
				throw new Exception("Any doctor with defined search parameters does not exists in the database.");
			}
			for (DoctorPo doctorPo : doctorPos) {
				Doctor doctor = (Doctor) mapperPoToDomain.mapUserPoToUser(doctorPo);
				removeSensitiveInfo(doctor);
				doctors.add(doctor);
			}
			GetDoctorsOutput result = new GetDoctorsOutput();
			result.setDoctors(doctors);
			return Response.status(Response.Status.OK).entity(result).build();
		} catch (Exception e) {
			return handleException(GetDoctorsOutput.class, e, Response.Status.BAD_REQUEST);
		}
	}

	public Response updateDoctor(UpdateDoctorInput updateDoctorInput) throws Throwable {
		try {
			Utils.checkObjectToNull(new String[] { "updateDoctorInput", "Doctor" }, updateDoctorInput, updateDoctorInput.getDoctor());
			int number = Integer.parseInt(updateDoctorInput.getDoctor().getNumber());
			DoctorPo doctorPo = abstractDao.findEntityByNumber(DoctorPo.class, number);
			doctorPo.setFirstname(updateDoctorInput.getDoctor().getFirstname());

			UpdateDoctorOutput result = new UpdateDoctorOutput();
			result.setDoctor((Doctor) mapperPoToDomain.mapUserPoToUser(doctorPo));

			return Response.status(Response.Status.OK).entity(result).build();
		} catch (Exception e) {
			return handleException(UpdateDoctorOutput.class, e, Response.Status.BAD_REQUEST);
		}
	}

	public Response deleteDoctor(DeleteDoctorInput deleteDoctorInput) throws Throwable {
		try {
			Utils.checkObjectToNull(new String[] { "deleteDoctorInput" }, deleteDoctorInput);
			DeleteDoctorOutput result = new DeleteDoctorOutput();
			userManagementDao.deleteDoctor(deleteDoctorInput.getNumber());
			result.setConfirm("Doctor successfuly deleted");

			return Response.status(Response.Status.CREATED).entity(result).build();
		} catch (Exception e) {
			return handleException(DeleteDoctorOutput.class, e, Response.Status.BAD_REQUEST);
		}

	}

	@WebMethod
	public Response createPatient(CreatePatientInput createPatientInput) throws Throwable {
		try {
			Utils.checkObjectToNull(new String[] { "createPatientInput" }, createPatientInput);
			Patient patient = createPatientInput.getPatient();
			Utils.checkObjectToNull(new String[] { "patient" }, patient);
			Utils.checkObjectToNull(new String[] { "InsuranceNumber" }, patient.getInsuranceNumber());
			List<AddressPo> addressesPo = new ArrayList<AddressPo>();
			List<PhonePo> phonesPo = new ArrayList<PhonePo>();

			InsurancePo insurancePo = abstractDao.findEntityByNumber(InsurancePo.class, patient.getInsuranceNumber());
			if (insurancePo == null) {
				throw new IllegalArgumentException("Can not find insurance by number [" + patient.getInsuranceNumber() + "] in database.");
			}

			if (patient.getAddress() != null) {
				for (Address address : patient.getAddress()) {
					Utils.checkObjectToNull(new String[] { "address.name", "address.street", "address.streetNum", "address.country",
							"address.zip", "address.city" }, address.getName(), address.getStreet(), address.getStreetNum(), address.getCountry(),
							address.getZip(), address.getCity());
					addressesPo.add(mapperDomainToPo.mapAddressToAddressPo(address));
				}
			}
			if (patient.getPhone() != null) {
				for (Phone phone : patient.getPhone()) {
					Utils.checkObjectToNull(new String[] { "phone.phoneNumber" }, phone.getPhoneNumber());
					phonesPo.add(mapperDomainToPo.mapPhoneToPhonePo(phone));
				}
			}
			PatientPo patientPo = userManagementDao.createPatient(patient.getFirstname(), patient.getMiddlename(), patient.getLastname(), addressesPo,
					patient.getNumber(), patient.getPassword(), patient.getEmail(), phonesPo, patient.getSalutation(), insurancePo);

			CreatePatientOutput result = new CreatePatientOutput();
			patient = (Patient) mapperPoToDomain.mapUserPoToUser(patientPo);
			removeSensitiveInfo(patient);
			result.setPatient(patient);

			return Response.status(Response.Status.CREATED).entity(result).build();
		} catch (Exception e) {
			return handleException(CreatePatientOutput.class, e, Response.Status.BAD_REQUEST);
		}
	}

	@WebMethod
	public Response getPatients(GetPatientsInput getPatientsInput) throws Throwable {
		try {
			Utils.checkObjectToNull(new String[] { "getPatientsInput" }, getPatientsInput);
			List<Patient> patients = new ArrayList<Patient>();
			List<PatientPo> patientPos = userManagementDao.getPatients(getPatientsInput.getNumber());
			if (patientPos.size() == 0) {
				throw new Exception("Any patient with defined search parameters does not exists in the database.");
			}
			for (PatientPo patientPo : patientPos) {
				Patient patient = (Patient) mapperPoToDomain.mapUserPoToUser(patientPo);
				removeSensitiveInfo(patient);
				patients.add(patient);
			}
			GetPatientsOutput result = new GetPatientsOutput();
			result.setPatients(patients);
			return Response.status(Response.Status.OK).entity(result).build();
		} catch (Exception e) {
			return handleException(GetPatientsOutput.class, e, Response.Status.BAD_REQUEST);
		}
	}

	public Response updatePatient(UpdatePatientInput updatePatientInput) throws Throwable {
		try {
			Utils.checkObjectToNull(new String[] { "updatePatientInput" }, updatePatientInput);
			int number = Integer.parseInt(updatePatientInput.getPatient().getNumber());
			PatientPo patientPo = abstractDao.findEntityByNumber(PatientPo.class, number);
			patientPo.setFirstname(updatePatientInput.getPatient().getFirstname());
			UpdatePatientOutput result = new UpdatePatientOutput();
			result.setPatient((Patient) mapperPoToDomain.mapUserPoToUser(patientPo));

			return Response.status(Response.Status.OK).entity(result).build();
		} catch (Exception e) {
			return handleException(UpdatePatientOutput.class, e, Response.Status.BAD_REQUEST);
		}
	}

	public Response deletePatient(DeletePatientInput deletePatientInput) throws Throwable {
		try {
			Utils.checkObjectToNull(new String[] { "deletePatientInput" }, deletePatientInput);
			DeletePatientOutput result = new DeletePatientOutput();
			userManagementDao.deletePatient(deletePatientInput.getNumber());
			result.setConfirm("Patient successfuly deleted");

			return Response.status(Response.Status.CREATED).entity(result).build();
		} catch (Exception e) {
			return handleException(DeletePatientOutput.class, e, Response.Status.BAD_REQUEST);
		}

	}

	@WebMethod
	public Response loginToHealthCard(LoginInput loginInput) throws Throwable {
		try {
			Utils
					.checkObjectToNull(new String[] { "loginInput", "number", "password" }, loginInput, loginInput.getNumber(), loginInput.getPassword());
			UserPo userPo = userManagementDao.loginToHealthCard(loginInput.getNumber(), loginInput.getPassword());
			if (userPo == null) {
				String error = "Login failed for user number: [" + loginInput.getNumber() + "] and password: [" + loginInput.getPassword()
						+ "]! Please log in with right credentials.";
				throw new Exception(error);
			}
			LoginOutput output = new LoginOutput();
			User user = mapperPoToDomain.mapUserPoToUser(userPo);
			removeSensitiveInfo(user);
			output.setUser(user);
			return Response.status(Response.Status.OK).entity(output).build();
		} catch (Exception e) {
			return handleException(LoginOutput.class, e, Response.Status.FORBIDDEN);
		}
	}

	private void removeSensitiveInfo(User user) {
		user.setPassword(null);
	}

	public MapperDomainToPo getMapperDomainToPo() {
		return mapperDomainToPo;
	}

	public void setMapperDomainToPo(MapperDomainToPo mapperDomainToPo) {
		this.mapperDomainToPo = mapperDomainToPo;
	}

	public MapperPOtoDomain getMapperPoToDomain() {
		return mapperPoToDomain;
	}

	public void setMapperPoToDomain(MapperPOtoDomain mapperPoToDomain) {
		this.mapperPoToDomain = mapperPoToDomain;
	}

	public UserManagementDao getUserManagementDao() {
		return userManagementDao;
	}

	public void setUserManagementDao(UserManagementDao userManagementDao) {
		this.userManagementDao = userManagementDao;
	}

	public void setAbstractDao(AbstractDao abstractDao) {
		this.abstractDao = abstractDao;
	}

	public AbstractDao getAbstractDao() {
		return abstractDao;
	}

}